﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace DOTNET101_Statement_Expression_Operators
{
    class Program
    {
		delegate int del(int i);
        delegate bool compareNum(int x, int y);
        delegate void someThing();
        delegate void TestDelegate(string s);
        delegate TResult Func<TArg0, TResult>(TArg0 arg0);
        delegate TResult Func<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1);

        static void Main(string[] args)
        {
            TestDelegates.Test();

            // Expression<del> = x => x * x;

            del myDelegate = x => x * x;
            int j = myDelegate(5); //j = 25

            compareNum num = (x, y) => x == y;
            bool result = num(3, 2);

            someThing thing = () => Console.WriteLine("Do something");
            thing();
           
            TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
            myDel("Hello");

            Func<int, bool> myFunc = x => x == 5;
            bool bFunc = myFunc(4); // returns false of course

            int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
            int oddNumbers = numbers.Count(n => n % 2 == 1);
            var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
            var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

            Form1 form = new Form1();
            form.ShowDialog();

            StartThread();

            // Test explicit conversion
            TestExplicitConversion.Test();

            TestComplex.Test();

            Console.ReadKey();
        }

		public static void StartThread()
		{
		    System.Threading.Thread t1 = new System.Threading.Thread
		      (delegate()
		            {
		                System.Console.Write("Hello, ");
		                System.Console.WriteLine("World!");
		            });
		    t1.Start();
		}

        bool IsPositive(int number)
        {
            if (number > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

    class TestDelegates
    {
        delegate void TestDelegate(string s);
        static void M(string s)
        {
            Console.WriteLine(s);
        }

        public static void Test()
        {
            // Original delegate syntax required 
            // initialization with a named method.
            TestDelegate testdelA = new TestDelegate(M);

            // C# 2.0: A delegate can be initialized with
            // inline code, called an "anonymous method." This
            // method takes a string as an input parameter.
            TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

            // C# 3.0. A delegate can be initialized with
            // a lambda expression. The lambda also takes a string
            // as an input parameter (x). The type of x is inferred by the compiler.
            TestDelegate testDelC = (x) => { Console.WriteLine(x); };

            // Invoke the delegates.
            testdelA("Hello. My name is M and I write lines.");
            testDelB("That's nothing. I'm anonymous and ");
            testDelC("I'm a famous author.");

            // Keep console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
    /* Output:
        Hello. My name is M and I write lines.
        That's nothing. I'm anonymous and
        I'm a famous author.
        Press any key to exit.
     */


    class TestLambda
    {
        delegate bool D();
        delegate bool D2(int i);
        static D del;
        static D2 del2;

        public static void TestMethod(int input)
        {
            int j = 0;
            // Initialize the delegates with lambda expressions.
            // Note access to 2 outer variables.
            // del will be invoked within this method.
            del = () => { j = 10; return j > input; };

            // del2 will be invoked after TestMethod goes out of scope.
            del2 = (x) => { return x == j; };

            // Demonstrate value of j:
            // Output: j = 0 
            // The delegate has not been invoked yet.
            Console.WriteLine("j = {0}", j);

            // Invoke the delegate.
            bool boolResult = del();

            // Output: j = 10 b = True
            Console.WriteLine("j = {0}. b = {1}", j, boolResult);
        }

        public static void TestStart()
        {
            TestMethod(5);

            // Prove that del2 still has a copy of
            // local variable j from TestMethod.
            bool result = del2(10);

            // Output: True
            Console.WriteLine(result);

            Console.ReadKey();
        }

        public static void TestWhere()
        {
            // Data source.
            int[] scores = { 90, 71, 82, 93, 75, 82 };

            // The call to Count forces iteration of the source
            int highScoreCount = scores.Where(n => n > 80).Count();

            Console.WriteLine("{0} scores are greater than 80", highScoreCount);

            // Outputs: 4 scores are greater than 80            
        }

        public class student
        {
            public int Year;
            public List<int> ExamScores;
        }

        private static void TotalsByGradeLevel()
        {
            List<student> students = new List<student>();

            // This query retrieves the total scores for First Year students, Second Years, and so on.
            // The outer Sum method uses a lambda in order to specify which numbers to add together.
            var categories =
            from student in students
            group student by student.Year into studentGroup
            select new { GradeLevel = studentGroup.Key, TotalScore = studentGroup.Sum(s => s.ExamScores.Sum()) };

            // Execute the query.   
            foreach (var cat in categories)
            {
                Console.WriteLine("Key = {0} Sum = {1}", cat.GradeLevel, cat.TotalScore);
            }
        }
        /*
             Outputs: 
             Key = SecondYear Sum = 1014
             Key = ThirdYear Sum = 964
             Key = FirstYear Sum = 1058
             Key = FourthYear Sum = 974
        */

    }

    struct Digit
    {
        byte value;

        public Digit(byte value)  //constructor
        {
            if (value > 9)
            {
                throw new System.ArgumentException();
            }
            this.value = value;
        }

        public static explicit operator Digit(byte b)  // explicit byte to digit conversion operator
        {
            Digit d = new Digit(b);  // explicit conversion

            System.Console.WriteLine("conversion occurred");
            return d;
        }

        public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
        {
            System.Console.WriteLine("conversion occurred");
            return d.value;  // implicit conversion
        }

    }

    public class TestExplicitConversion
    {
        public static void Test()
        {
            try
            {
                byte b = 3;
                Digit d = (Digit)b;  // explicit conversion

                b = d;  // implicit conversion -- no cast needed
            }
            catch (System.Exception e)
            {
                System.Console.WriteLine("{0} Exception caught.", e);
            }
        }
    }

    public class Complex
    {
        public int real;
        public int imaginary;

        public Complex(int real, int imaginary)  //constructor
        {
            this.real = real;
            this.imaginary = imaginary;
        }

        // Declare which operator to overload (+),
        // the types that can be added (two Complex objects),
        // and the return type (Complex):
        public static Complex operator +(Complex c1, Complex c2)
        {
            return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
        }

        // Override the ToString() method to display a complex number in the traditional format:
        public override string ToString()
        {
            return (System.String.Format("{0} + {1}i", real, imaginary));
        }
    }

    public class TestComplex
    {
        public static void Test()
        {
            Complex num1 = new Complex(2, 3);
            Complex num2 = new Complex(3, 4);

            // Add two Complex objects through the overloaded plus operator:
            Complex sum = num1 + num2;

            // Print the numbers and the sum using the overriden ToString method:
            System.Console.WriteLine("First complex number:  {0}", num1);
            System.Console.WriteLine("Second complex number: {0}", num2);
            System.Console.WriteLine("The sum of the two numbers: {0}", sum);
        }
    }

    public class TwoDPoint : System.Object
    {
        public readonly int x, y;

        public TwoDPoint(int x, int y)  //constructor
        {
            this.x = x;
            this.y = y;
        }

        public override bool Equals(System.Object obj)
        {
            // If parameter is null return false.
            if (obj == null)
            {
                return false;
            }

            // If parameter cannot be cast to Point return false.
            TwoDPoint p = obj as TwoDPoint;
            if ((System.Object)p == null)
            {
                return false;
            }

            // Return true if the fields match:
            return (x == p.x) && (y == p.y);
        }

        public bool Equals(TwoDPoint p)
        {
            // If parameter is null return false:
            if ((object)p == null)
            {
                return false;
            }

            // Return true if the fields match:
            return (x == p.x) && (y == p.y);
        }

        public override int GetHashCode()
        {
            return x ^ y;
        }
    }

    class ThreeDPoint : TwoDPoint
    {
        public readonly int z;

        public ThreeDPoint(int x, int y, int z)
            : base(x, y)
        {
            this.z = z;
        }

        public override bool Equals(System.Object obj)
        {
            // If parameter cannot be cast to ThreeDPoint return false:
            ThreeDPoint p = obj as ThreeDPoint;
            if ((object)p == null)
            {
                return false;
            }

            // Return true if the fields match:
            return base.Equals(obj) && z == p.z;
        }

        public bool Equals(ThreeDPoint p)
        {
            // Return true if the fields match:
            return base.Equals((TwoDPoint)p) && z == p.z;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode() ^ z;
        }

        //add this code to class ThreeDPoint as defined previously
        //
        // A common error in overloads of operator == is to use (a == b), 
        // (a == null), or (b == null) to check for reference equality.
        // This instead creates a call to the overloaded operator ==, 
        // causing an infinite loop. Use ReferenceEquals or cast the type
        // to Object, to avoid the loop.

        public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            // If one is null, but not both, return false.
            if (((object)a == null) || ((object)b == null))
            {
                return false;
            }

            // Return true if the fields match:
            return a.x == b.x && a.y == b.y && a.z == b.z;
        }

        public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
        {
            return !(a == b);
        }
    }
}
